import os
import tkinter as tk
from tkinter import filedialog
from PIL import Image, ImageTk
import numpy as np
import cv2
from skimage.metrics import structural_similarity as ssim
from skimage.transform import resize
import hashlib

# 环境配置 python 3.7 ,opencv-python==4.1.2.30
# pip install opencv-python==4.1.2.30 -i https://pypi.tuna.tsinghua.edu.cn/simple
 
class ImageMatcher(tk.Frame):
    def __init__(self, master=None, **kw):
        super().__init__(master, **kw)
        self.master = master
        self.create_widgets()
    def create_widgets(self):
        # 创建左半部分控件
        self.left_frame = tk.Frame(self)
        self.left_frame.pack(side='left', fill='both', expand=True)
 
        self.image_frame = tk.Frame(self.left_frame, bg='gray')
        self.image_frame.pack(side='top', fill='both', expand=True, padx=10, pady=10)
 
        self.upload_button = tk.Button(self.left_frame, text='上传图像', command=self.load_image)
        self.upload_button.pack(side='left', padx=10, pady=10)
 
        self.match_button = tk.Button(self.left_frame, text='图像匹配', command=self.match_images)
        self.match_button.pack(side='left', padx=10, pady=10)
 
        # 创建右半部分控件
        self.right_frame = tk.Frame(self)
        self.right_frame.pack(side='left', fill='both', expand=True)
 
        self.image1_frame = tk.Frame(self.right_frame, bg='gray')
        self.image1_frame.pack(side='top', fill='both', expand=True, padx=10, pady=10)
 
        self.similarity1_label = tk.Label(self.right_frame, text='相似度：0.00')
        self.similarity1_label.pack(side='top', padx=10, pady=10)
 
        self.image2_frame = tk.Frame(self.right_frame, bg='gray')
        self.image2_frame.pack(side='top', fill='both', expand=True, padx=10, pady=10)
 
        self.similarity2_label = tk.Label(self.right_frame, text='相似度：0.00')
        self.similarity2_label.pack(side='top', padx=10, pady=10)
 
        self.image3_frame = tk.Frame(self.right_frame, bg='gray')
        self.image3_frame.pack(side='top', fill='both', expand=True, padx=10, pady=10)
 
        self.similarity3_label = tk.Label(self.right_frame, text='相似度：0.00')
        self.similarity3_label.pack(side='top', padx=10, pady=10)
 
    def load_image(self):
        # 打开文件选择对话框，选择要上传的图像文件
        filetypes = (("JPEG files", "*.jpg"), ("PNG files", "*.png"))
        self.imgName = filedialog.askopenfilename(title="选择图像文件", filetypes=filetypes)
 
        # 如果选择了文件，则使用 PIL 库打开并显示图像
        if self.imgName:
            # 清除先前显示的图像
            self.clear_images()
            image = Image.open(self.imgName)
            image = self.resize_image(image, self.image_frame.winfo_width(), self.image_frame.winfo_height())
            photo = ImageTk.PhotoImage(image)
            self.image_label = tk.Label(self.image_frame, image=photo)
            self.image_label.pack(side='top', fill='both', expand=True)
            self.image_label.image = photo  # 保持对 PhotoImage 对象的引用，以免被 Python 的垃圾回收机制销毁
            image.close()
 
    def match_images(self):
        # 如果没有上传图像，则不执行匹配操作
        if not hasattr(self, 'image_label'):
            return
        # 加载所有图像文件，并使用 OpenCV 库计算它们与上传的图像的相似度
        folder = os.path.dirname(self.imgName)
        filenames = os.listdir(folder)
        filenames = [f'{folder}/{f}' for f in filenames if f.endswith('.jpg') or f.endswith('.png')]
        filenames.remove(self.imgName)
        
        similarities1 = []
        similarities2 = []
        similarities3 = []
        similarities4 = []
        for fileItem in filenames:
            print("filename:",self.imgName)
            print("target filename:",fileItem)
            similarity_1,similarity_2,similarity_3,similarity_4  = self.calculate_similarity(self.imgName, fileItem)
            similarities1.append((fileItem, similarity_1))
            similarities2.append((fileItem, similarity_2))
            similarities3.append((fileItem, similarity_3))
            similarities4.append((fileItem, similarity_4))
        # 根据相似度从高到低排序，并取出前三个最相似的图像
        similarities1.sort(key=lambda x: x[1], reverse=False)
        similarities2.sort(key=lambda x: x[1], reverse=False)
        similarities3.sort(key=lambda x: x[1], reverse=True)
        similarities4.sort(key=lambda x: x[1], reverse=True)

        top_similarities = [similarities1[0],similarities2[0],similarities3[0],similarities4[0]]
 
        # 在右半部分的三个区域中分别显示这三张图像
        for i, (fileItem, similarity) in enumerate(top_similarities):
            image = Image.open(fileItem)
            image = self.resize_image(image, self.image1_frame.winfo_width(), self.image1_frame.winfo_height())
            photo = ImageTk.PhotoImage(image)
            label = tk.Label(self.right_frame, image=photo)
            label.image = photo
            if i == 0:
                label.pack(in_=self.image1_frame, side='top', fill='both', expand=True)
                self.similarity1_label.config(text=f'均值哈希算法相似度：{similarity:.2f}')
            elif i == 1:
                label.pack(in_=self.image2_frame, side='top', fill='both', expand=True)
                self.similarity2_label.config(text=f'差值哈希算法相似度：{similarity:.2f}')
            elif i == 2:
                #label.pack(in_=self.image3_frame, side='top', fill='both', expand=True)
                #self.similarity3_label.config(text=f'cos相似度算法相似度：{similarity:.2f}')
                print("cos相似度算法相似度：",similarity)
            elif i == 3:
                label.pack(in_=self.image3_frame, side='top', fill='both', expand=True)
                self.similarity3_label.config(text=f'ssim：{similarity:.2f}')
    def clear_images(self):
        # 清除左半部分中的图像和右半部分中的标注
        if hasattr(self, 'image_label'):
            self.image_label.pack_forget()
            del self.image_label
 
        for label in [self.similarity1_label, self.similarity2_label, self.similarity3_label]:
            label.config(text='相似度：0.00')
 
        for frame in [self.image1_frame, self.image2_frame, self.image3_frame]:
            for widget in frame.winfo_children():
                widget.pack_forget()
 
    # 将图像按照指定的宽度和高度等比例缩放
    def resize_image(self, image, width, height):
        # 将图像按照指定的宽度和高度等比例缩放
        w, h = image.size
        ratio = min(width / w, height / h)
        size = (int(w * ratio), int(h * ratio))
        return image.resize(size)
    def dHash(self,img):
        # 缩放8*8
        img = cv2.resize(img, (9, 8), interpolation=cv2.INTER_CUBIC)
        # 转换灰度图
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        hash_str = ''
        # 每行前一个像素大于后一个像素为1，相反为0，生成哈希
        for i in range(8):
            for j in range(8):
                if gray[i, j] > gray[i, j + 1]:
                    hash_str = hash_str + '1'
                else:
                    hash_str = hash_str + '0'
        return hash_str
    def aHash(self,img):
        # 缩放为8*8
        img = cv2.resize(img, (8, 8), interpolation=cv2.INTER_CUBIC)
        # 转换为灰度图
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        # s为像素和初值为0，hash_str为hash值初值为''
        s = 0
        hash_str = ''
        # 遍历累加求像素和
        for i in range(8):
            for j in range(8):
                s = s + gray[i, j]
        # 求平均灰度
        avg = s / 64
        # 灰度大于平均值为1相反为0生成图片的hash值
        for i in range(8):
            for j in range(8):
                if gray[i, j] > avg:
                    hash_str = hash_str + '1'
                else:
                    hash_str = hash_str + '0'
        return hash_str
    def cmpHash(self,hash1, hash2):
        n = 0
        # hash长度不同则返回-1代表传参出错
        if len(hash1) != len(hash2):
            return -1
        # 遍历判断
        for i in range(len(hash1)):
            # 不相等则n计数+1，n最终为相似度
            if hash1[i] != hash2[i]:
                n = n + 1
        return n
    
    #计算相似度
    def calculate_similarity(self, image1, image2):
        #image1=image1.split("/")[-2]+"/"+image1.split("/")[-1]
        #image2=image2.split("/")[-2]+"/"+image2.split("/")[-1]
        print("image1:", image1)
        print("image2:", image2)
        #path1_byte = image1.encode('gbk')
        #path2_byte = image2.encode('gbk')

        img1 = cv2.imdecode(np.fromfile(image1, dtype=np.uint8), -1)
        # imdecode读取的是rgb，如果后续需要opencv处理的话，需要转换成bgr，转换后图片颜色会变化
        img1 = cv2.cvtColor(img1, cv2.COLOR_RGB2BGR)
        # 方法1  均值哈希算法相似度 计算两张图像的相似度
        #img1 = cv2.imread(path1_byte.decode())
        #cv2.imshow('IMREAD_UNCHANGED+Color',img1)
        #cv2.waitKey()
        img2 = cv2.imdecode(np.fromfile(image2, dtype=np.uint8), -1)
        # imdecode读取的是rgb，如果后续需要opencv处理的话，需要转换成bgr，转换后图片颜色会变化
        img2 = cv2.cvtColor(img2, cv2.COLOR_RGB2BGR)
        #img2 = cv2.imread(path2_byte.decode())
        hash1 = self.aHash(img1)
        hash2 = self.aHash(img2)
        similarity_1= self.cmpHash(hash1, hash2)
 
        # 方法2   差值哈希算法相似度 计算两张图像的相似度
        hash1 = self.dHash(img1)
        hash2 = self.dHash(img2)
        similarity_2= self.cmpHash(hash1, hash2)
        
        # 方法3 计算两张图像的相似度
        print("image1, image2:",image1, image2)
        image1 = np.array(Image.open(image1))
        image2 = np.array(Image.open(image2))
        arr1_flat = image1.reshape(-1)
        arr2_flat = image2.reshape(-1)
        if(arr1_flat.shape != arr2_flat.shape):
            similarity_3 = 0
        else:
            num_equal = np.sum(arr1_flat == arr2_flat)
            similarity_3 = num_equal / len(arr1_flat)
        #计算方法4
        #cv2.compare_ssim(img1, img2)
        (score, diff) = calculate_ssim(img1, img2, channel_axis=2)
        print('均值哈希算法相似度：', similarity_1,'差值哈希算法相似度：', similarity_2,"np.sum:",similarity_3)
        return similarity_1/100,similarity_2/100,similarity_3,score
        # print("111")
        # orb = cv2.ORB_create()
        # keypoints1, descriptors1 = orb.detectAndCompute(image1, None)
        # keypoints2, descriptors2 = orb.detectAndCompute(image2, None)
        #
        # if len(keypoints1) == 0 or len(keypoints2) == 0:
        #     return 0
        # bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
        # matches = bf.match(descriptors1, descriptors2)
        # matches = sorted(matches, key=lambda x: x.distance)
        # num_matches = min(len(matches), 100)
        # similarity = sum([matches[i].distance for i in range(num_matches)]) / num_matches
        # return similarity

#封装ssim
def calculate_ssim(imageA, imageB, channel_axis=None):
    # 确保图像尺寸相同
    if imageA.shape != imageB.shape:
        imageB = resize(imageB, imageA.shape[:2], preserve_range=True).astype(imageA.dtype)
    """计算两个图像之间的 SSIM，自动处理窗口大小和通道"""
    # 确保图像尺寸足够大
    min_size = min(imageA.shape[:2])
    win_size = min(7, min_size)
    if win_size < 3:
        # 图像尺寸过小，考虑缩放或其他处理
        raise ValueError("图像尺寸过小，无法计算 SSIM")
    if win_size % 2 == 0:
        win_size -= 1  # 确保窗口大小是奇数
    # 检查是否为多通道图像
    is_multichannel = channel_axis is not None
    # 计算 SSIM
    return ssim(imageA, imageB, 
                full=True, 
                multichannel=is_multichannel,
                channel_axis=channel_axis,
                win_size=win_size)
#直接比较文件md5,相同表示两个文件相同
def md5_similarity(img1_path, img2_path):
    file1 = open(img1_path, "rb")
    file2 = open(img2_path, "rb")
    md = hashlib.md5()
    md.update(file1.read())
    res1 = md.hexdigest()
    md = hashlib.md5()
    md.update(file2.read())
    res2 = md.hexdigest()
    return res1 == res2

#主代码段
if __name__ == '__main__':
    root = tk.Tk()
    root.geometry('1000x600+400+200')
    root.title('图像匹配系统')
 
    app = ImageMatcher(master=root)
    app.pack(fill='both', expand=True)
 
    root.mainloop()

